package com.leyou.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.client.BrandClient;
import com.leyou.client.CategoryClient;
import com.leyou.client.GoodsClient;
import com.leyou.client.SpecificationClient;
import com.leyou.item.pojo.*;
import com.leyou.pojo.Goods;
import com.leyou.pojo.SearchRequest;
import com.leyou.pojo.SearchResult;
import com.leyou.reponsitory.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private BrandClient brandClient;//查询品牌需要
    @Autowired
    private CategoryClient categoryClient;//查询分类
    @Autowired
    private GoodsClient goodsClient;//查询SKU需要
    @Autowired
    private SpecificationClient specificationClient;
    @Autowired
    private GoodsRepository goodsRepository;

    private static final ObjectMapper MAPPER =new ObjectMapper();

    /**
     * 把SPU转换为GOODS
     * @param spu
     * @return
     * @throws IOException
     */
    public Goods buildGoods(Spu spu) throws IOException {

        Goods goods = new Goods();
        //根据品牌id查询品牌
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());

        //根据cid1，cid2，cid3查询分类
        List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        //根据SPU的id查询对应的sku
        List<Sku> skus = goodsClient.querySkusBySpuId(spu.getId());
        //设置价格
        List<Long> prices = new ArrayList<>();

        //仅取几个需要的SKU字段。
        //一个MAP代表一个SKU，key的取值：id  title  image  price
        List<Map<String,Object>>  skuMapList= new ArrayList<>();

        for (Sku sku : skus) {
            prices.add(sku.getPrice());

            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            //进行图片的切割，只需要第一张图片
            skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(),
                            ",")[0] : "");
            skuMapList.add(skuMap);
        }

        // 查询spuDetail。目的拿到genericSpec 、SpecialSpec
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId());
        //获取通用的规格参数
        // 反序列化为一个MAP，本身存储的就GenericSpec就是一个JSon
        //{"1":"其它","2":"G9青春版（全网通版）","3":2016.0,"5":143,"6":"其它","7":"Android","8":"骁龙（Snapdragon)","9":"骁龙617（msm8952）","10":"八核","11":1.5,"14":5.2,"15":"1920*1080(FHD)","16":800.0,"17":1300.0,"18":3000.0}
        Map<Long, Object> genericSpecMap =
                MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {});

        // 获取特殊的规格参数 SpecialSpec：{"4":["银钻灰","琥珀金","玫瑰金"],"12":["4GB"],"13":["64GB"]}
        Map<Long, List<Object>> specialSpecMap =
                MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>(){});

        //查询所有的搜索规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), null, true);
        // 定义map接收{规格参数名，规格参数值}
        Map<String, Object> specs = new HashMap<>();
        for (SpecParam param : params) {
            //判断规格参数是否是通用的
            if(param.getGeneric()){
                //是通用的
                String value = genericSpecMap.get(param.getId()).toString();
                //判断是否是数值类型
                if(param.getNumeric()){
                    //如果是数值类型的参数，返回范围
                    value=chooseSegment(value,param);
                }
                specs.put(param.getName(),value);
            }else {
                //特殊的规格参数
                List<Object> value = specialSpecMap.get(param.getId());
                specs.put(param.getName(),value);
            }
        }

        //把简单的参数复制给goods
        BeanUtils.copyProperties(spu,goods);
        goods.setAll(spu.getTitle()+" "+brand.getName()+" "+ StringUtils.join(names," ")); // ALL----所有需要被搜索的信息，包含标题，分类，甚至品牌
        goods.setPrice(prices);
        //把集合转换为JSOn，用到的jackson工具类
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        goods.setSpecs(specs);

        return goods;
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 基本查询
     * @param request
     * @return
     */
    public SearchResult search(SearchRequest request) {
        //判断查询条件是否为空
        if(StringUtils.isBlank(request.getKey())){
            return null;
        }
        //创建自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1、对key进行全文检索,多个词的使用AND
//        MatchQueryBuilder basicQuery = QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND);
        BoolQueryBuilder basicQuery =buildBasicQuery(request);//带有参数的过滤查询
        queryBuilder.withQuery(basicQuery) ;

        //2、分页
        int page = request.getPage();
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page -1,size));

        //3、添加结果集过滤。通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        FetchSourceFilter sourceFilter = new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null);
        queryBuilder.withSourceFilter(sourceFilter);

        //第二次添加： 品牌和分类的聚合。页面的头部
        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));

        //4、执行查询
//        NativeSearchQuery build = queryBuilder.build();
        AggregatedPage<Goods> goodsPage =   (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());

        //解析聚合结果集
        List<Map<String, Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));

        //规格参数判断
        List<Map<String,Object>> specs =null;
            //分类只有一个情况下进行聚合
        if(!CollectionUtils.isEmpty(categories) && categories.size() == 1){
            specs =getParamAggName((Long)categories.get(0).get("id"),basicQuery);
        }

        //返回分页结果集
        return  new SearchResult(goodsPage.getTotalElements(),goodsPage.getTotalPages(),goodsPage.getContent(),categories,brands,specs);
    }

    /**
     *构建BoolQueryBulid
     * @Date  2019/12/31 15:16
     * @param request 
     * @return 
     **/
    private BoolQueryBuilder buildBasicQuery(SearchRequest request) {
        //初始化布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //添加基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 添加过滤条件
        if (CollectionUtils.isEmpty(request.getFilter())){
            return boolQueryBuilder;
        }
        for (Map.Entry<String, String> entry : request.getFilter().entrySet()) {

            String key = entry.getKey();
            // 如果过滤条件是“品牌”, 过滤的字段名：brandId
            if (StringUtils.equals("品牌", key)) {
                key = "brandId";
            } else if (StringUtils.equals("分类", key)) {
                // 如果是“分类”，过滤字段名：cid3
                key = "cid3";
            } else {
                // 如果是规格参数名，过滤字段名：specs.key.keyword
                key = "specs." + key + ".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;
    }

    /**
     *规格参数的聚合
     * @Date  2019/12/31 10:39
     * @param id
     * @param basicQuery
     * @return
     **/
    private List<Map<String,Object>> getParamAggName(Long id, QueryBuilder basicQuery) {
        //查询聚合的规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null, id, null, true);
        //自定义查询构建器，
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //添加基本查询条件
        queryBuilder.withQuery(basicQuery);
        //添加规格参数的聚合
        params.forEach(param -> {//根据名称进行词条聚合，
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
        });

        //添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));

        //执行查询,原来是Page<Goods>
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());

        // 初始化聚合结果集
        List<Map<String, Object>> paramMapList = new ArrayList<>();

        // 遍历解析聚合查询的结果集
        Map<String, Aggregation> paramAggregationMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation> entry : paramAggregationMap.entrySet()) {
            //每一个规格参数的集合结果集，对应一个map
            Map<String, Object> map = new HashMap<>();
            // 放入规格参数名
            map.put("k", entry.getKey());

            // 收集规格参数值
//            List<Object> options = new ArrayList<>();
            // 解析每个聚合
            StringTerms terms = (StringTerms)entry.getValue();
            // 遍历每个聚合中桶，把桶中key放入收集规格参数的集合中
//            terms.getBuckets().forEach(bucket -> options.add(bucket.getKeyAsString()));
            List<Object> options=terms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            map.put("options", options);
            paramMapList.add(map);
        }
        return paramMapList;
    }


        /**
         * @Date  2019/12/31 10:36
         * @param aggregation
         * @return
         **/
    private List<Map<String,Object>> getCategoryAggResult(Aggregation aggregation) {
        LongTerms terms =(LongTerms)aggregation;
        //获取桶集合

        return terms.getBuckets().stream().map(bucket -> {
            HashMap<String, Object> map = new HashMap<>();

            Long id = bucket.getKeyAsNumber().longValue();
            List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(id));

            map.put("id",id);
            map.put("name",names.get(0));
            return map;
        }).collect(Collectors.toList());
    }

    /**
     *解析品牌的聚合结果集
     * @param aggregation
     * @return
     */
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        LongTerms terms =(LongTerms)aggregation;
        //获取桶集合
        return terms.getBuckets().stream().map(bucket -> {

            Long id = bucket.getKeyAsNumber().longValue();
            return this.brandClient.queryBrandById((id));
        }).collect(Collectors.toList());
    }

    public void createIndex(Long spuId) throws IOException {
        Spu spu = this.goodsClient.querySpuById(spuId);
        Goods goods = this.buildGoods(spu);
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long spuId ){
        this.goodsRepository.deleteById(spuId);

    }
}
